/*-
 * Copyright (c) 2014 Shang-Rong Cai. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $ DIDF: diddraw.c    Rev. 0.1.4    2014-04-16T22:52+08:00 $
 */

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <complex.h>

#include <png.h>

#include <did.h>


/* Default Image Size */
#define DEFAULT_MAXIMUM_HEIGHT 1600
#define DEFAULT_MAXIMUM_WIDTH  1600

#define DEFAULT_HEIGHT 800
#define DEFAULT_WIDTH  800

#define DEFAULT_MINIMUM_HEIGHT 100
#define DEFAULT_MINIMUM_WIDTH  100

/* Value Item Maximum Size For Buffer */
#define VALUE_BUFFER_SIZE 16


/* Image Pixel (RGBA) */
typedef struct
{
    uint8_t ccr;  /* Color component: red */
    uint8_t ccg;  /* Color component: green */
    uint8_t ccb;  /* Color component: blue */
    uint8_t cca;  /* Color component: alpha */
} img_pixel_t;


/* Image Frame */
typedef struct
{
    size_t frame_wdth;  /* Frame width */
    size_t frame_hght;  /* Frame height */
    img_pixel_t *pixel_list;  /* List of pixel in row oriented */
} img_frame_t;

int img_frame_init(img_frame_t *ifrm, const size_t wz, const size_t hz);

int img_frame_dest(img_frame_t *ifrm);


/* General Compressed First Index Format Without Value Item */
typedef struct
{
    size_t fst_idx_sz;  /* First index bound */
    size_t snd_idx_sz;  /* Second index bound */
    size_t dat_ent_sz;  /* Number of data entry */

    void *fst_dst;  /* Distribution of indexed data depended on first index */
    void *idx_lst;  /* Index list */
} cfid_t;

int cfid_init(const size_t fsz, const size_t ssz, const size_t dsz, uint8_t ityp, cfid_t *cfid);

int cfid_dest(cfid_t *cfid);

int cfid_read_did(FILE *fpif, did_head_t *hdprt, did_meta_t *mtprt, cfid_t *cfid);


void usage();


size_t set_proper_hight(size_t uimgh, size_t h_min, size_t h_max);

size_t set_proper_width(size_t uimgw, size_t w_min, size_t w_max);


int render_sparsity(const cfid_t *cfid, uint8_t ityp, img_frame_t *ifrm);

int save_img_frame_png(const img_frame_t *ifrm, const char *fnof);


int main(int argc, char *argv[])
{
    int ierr = ERRNO_UNDEFINED;

    int cur_argc;
    char **cur_argv;

    int opti;

    size_t img_hght;  /* Image height */
    size_t img_wdth;  /* Image width */
    size_t buf_size;

    did_head_t hdprt;
    did_meta_t mtprt;

    FILE *fpif;

    cfid_t cfid;  /* For part of DID body data */

    img_frame_t ifrm;

    /* Initialize */
    img_hght = DEFAULT_HEIGHT;
    img_wdth = DEFAULT_WIDTH;

    buf_size = DEFAULT_WIDTH;

    cur_argc = argc;
    cur_argv = argv;

    while ((opti = getopt(cur_argc, cur_argv, "h:w:")) != -1)
    {
        switch (opti)
        {
            case 'h':
                //sscanf(optarg, "%zd", &buf_size);
                sscanf(optarg, "%zd", &img_hght);
                break;
            case 'w':
                //sscanf(optarg, "%zd", &buf_size);
                sscanf(optarg, "%zd", &img_wdth);
                break;
        }
    }

    printf("User define size: %zd\n", buf_size);

    /* Check Height And Width */
    img_hght = set_proper_hight(img_hght, DEFAULT_MINIMUM_HEIGHT, DEFAULT_MAXIMUM_HEIGHT);
    img_wdth = set_proper_width(img_wdth, DEFAULT_MINIMUM_WIDTH, DEFAULT_MAXIMUM_WIDTH);

    if (cur_argc < 3)
    {
        usage();
        goto EXIT_MAIN;
    }
    else
    {
        fpif = fopen(cur_argv[cur_argc - 2], "r");

        if (fpif == NULL)
        {
            perror(cur_argv[cur_argc - 2]);
            goto EXIT_MAIN;
        }
    }

    /* Read Sparsity */
    ierr = did_read_head(fpif, &hdprt);
    ierr = did_read_meta(fpif, &mtprt);

    ierr = cfid_init(mtprt.fst_idx_sz, mtprt.snd_idx_sz, mtprt.dat_ent_sz, hdprt.index_type, &cfid);
    ierr = cfid_read_did(fpif, &hdprt, &mtprt, &cfid);

    /* Picture Frame */
    ierr = img_frame_init(&ifrm, img_wdth, img_hght);

    ierr = render_sparsity(&cfid, hdprt.index_type, &ifrm);

    /* Save Image To File With PNG Format */
    ierr = save_img_frame_png(&ifrm, cur_argv[cur_argc - 1]);

    /* Finalize */
    ierr = cfid_dest(&cfid);
    ierr = img_frame_dest(&ifrm);

    EXIT_MAIN:

    return ierr;
}


void usage()
{
    printf("usage: didview [-h height | -w width] did-file image-file\n");
}


size_t set_proper_hight(size_t uimgh, size_t h_min, size_t h_max)
{
    size_t result;

    result = uimgh;

    if (uimgh < DEFAULT_MINIMUM_HEIGHT || uimgh > DEFAULT_MAXIMUM_HEIGHT)
    {
        printf("The height should be in the range from %zd to %zd\n", h_min, h_max);
        printf("Set height default\n");
        result = DEFAULT_HEIGHT;
    }

    return result;
}


size_t set_proper_width(size_t uimgw, size_t w_min, size_t w_max)
{
    size_t result;

    result = uimgw;

    if (uimgw < DEFAULT_MINIMUM_WIDTH || uimgw > DEFAULT_MAXIMUM_WIDTH)
    {
        printf("The width should be in the range from %zd to %zd\n", w_min, w_max);
        printf("Set width default\n");
        result = DEFAULT_WIDTH;
    }

    return result;
}


int img_frame_init(img_frame_t *ifrm, const size_t wz, const size_t hz)
{
    int ierr = ERRNO_UNDEFINED;

    ifrm->frame_wdth = wz;
    ifrm->frame_hght = hz;

    ifrm->pixel_list = (img_pixel_t*) calloc(ifrm->frame_wdth * ifrm->frame_hght, sizeof(img_pixel_t));

    return ierr;
}


int img_frame_dest(img_frame_t *ifrm)
{
    int ierr = ERRNO_UNDEFINED;

    free(ifrm->pixel_list);

    ifrm->frame_wdth = 0;
    ifrm->frame_hght = 0;

    return ierr;
}


int cfid_init(const size_t fsz, const size_t ssz, const size_t dsz, uint8_t ityp, cfid_t *cfid)
{
    int ierr = ERRNO_UNDEFINED;

    size_t index_size;

    cfid->fst_idx_sz = fsz;
    cfid->snd_idx_sz = ssz;
    cfid->dat_ent_sz = dsz;

    switch (ityp)
    {
        case DID_HEAD_INDEX_T_I32:
            index_size = sizeof(int32_t);
            break;
        case DID_HEAD_INDEX_T_I64:
            index_size = sizeof(int64_t);
            break;
        default:
            ierr = ERRNO_UNKNOWN_TYPE;
            goto EXIT_CFID_INIT;
            break;
    }

    cfid->fst_dst = calloc(cfid->fst_idx_sz + 1, index_size);
    cfid->idx_lst = calloc(cfid->dat_ent_sz, index_size);

    EXIT_CFID_INIT:

    return ierr;
}


int cfid_dest(cfid_t *cfid)
{
    int ierr = ERRNO_UNDEFINED;

    free(cfid->fst_dst);
    free(cfid->idx_lst);

    return ierr;
}


void read_index_i32(FILE *fpif, uint64_t sz, size_t ignz, int32_t *dst, int32_t *idx)
{
    uint64_t ui, uj;

    uint8_t buf_val[VALUE_BUFFER_SIZE];

    for (ui = 0; ui < sz; ui++)
    {
        for (uj = dst[ui]; uj < dst[ui + 1]; uj++)
        {
            fread((void*) &idx[uj], sizeof(int32_t), 1, fpif);
            fread((void*) &buf_val, ignz, 1, fpif);
        #ifdef DEBUG_INFO
            printf("%7ld: (%7ld, %7d)\n", uj, ui, idx[uj]);
        #endif
        }
    }
}


void read_index_i64(FILE *fpif, uint64_t sz, size_t ignz, int64_t *dst, int64_t *idx)
{
    uint64_t ui, uj;

    uint8_t buf_val[VALUE_BUFFER_SIZE];

    for (ui = 0; ui < sz; ui++)
    {
        for (uj = dst[ui]; uj < dst[ui + 1]; uj++)
        {
            fread((void*) &idx[uj], sizeof(int64_t), 1, fpif);
            fread((void*) &buf_val, ignz, 1, fpif);
        #ifdef DEBUG_INFO
            printf("%7ld: (%7ld, %7ld)\n", uj, ui, idx[uj]);
        #endif
        }
    }
}


int cfid_read_did(FILE *fpif, did_head_t *hdprt, did_meta_t *mtprt, cfid_t *cfid)
{
    int ierr = ERRNO_UNDEFINED;

    size_t idx_sz;
    size_t val_sz;
    /* Initialize */
    idx_sz = 0;
    val_sz = 0;

    switch (hdprt->index_type)
    {
        case DID_HEAD_INDEX_T_I32:
            idx_sz = sizeof(int32_t);
            break;
        case DID_HEAD_INDEX_T_I64:
            idx_sz = sizeof(int64_t);
            break;
    }

    switch (hdprt->entry_type)
    {
        case DID_HEAD_DATA_T_F32:
            val_sz = sizeof(float);
            break;
        case DID_HEAD_DATA_T_F64:
            val_sz = sizeof(double);
            break;
        case DID_HEAD_DATA_T_C32:
            val_sz = sizeof(float complex);
            break;
        case DID_HEAD_DATA_T_C64:
            val_sz = sizeof(double complex);
            break;
        case DID_HEAD_DATA_T_I32:
            val_sz = sizeof(int32_t);
            break;
        case DID_HEAD_DATA_T_I64:
            val_sz = sizeof(int64_t);
            break;
    }

    /* Distribution Depended On First Index */
    fseek(fpif, mtprt->fst_idx_dst_addr, SEEK_SET);
    fread(cfid->fst_dst, idx_sz, (size_t) mtprt->fst_idx_sz + 1, fpif);

    fseek(fpif, mtprt->idx_dat_lst_addr, SEEK_SET);

    switch (hdprt->index_type)
    {
        case DID_HEAD_INDEX_T_I32:
            read_index_i32(fpif, mtprt->fst_idx_sz, val_sz, cfid->fst_dst, cfid->idx_lst);
            break;
        case DID_HEAD_INDEX_T_I64:
            read_index_i64(fpif, mtprt->fst_idx_sz, val_sz, cfid->fst_dst, cfid->idx_lst);
            break;
    }

    return ierr;
}


int render_sparsity_i32(const cfid_t *cfid, img_frame_t *ifrm)
{
    int ierr = ERRNO_UNDEFINED;

    size_t zi;
    int ii, ij, ik;

    int pxl_idx_f;
    int pxl_idx_s;
    int draw_bkz_w;  /* Draw block size of pixel (at least 1) for width */
    int draw_bkz_h;  /* Draw block size of pixel (at least 1) for height */

    int ifrm_pxl_idx_f;
    int ifrm_pxl_idx_s;
    int ifrm_pxl_idx;

    int32_t *dst_32, *idx_32;

    draw_bkz_w = ifrm->frame_wdth / (int) cfid->snd_idx_sz;

    if (draw_bkz_w == 0)
    {
        draw_bkz_w = 1;
    }

    draw_bkz_h = ifrm->frame_hght / (int) cfid->fst_idx_sz;

    if (draw_bkz_h == 0)
    {
        draw_bkz_h = 1;
    }

#ifdef DEBUG_INFO
    printf("Drawing Block Size: %d by %d\n", draw_bkz_w, draw_bkz_h);
#endif

    /* Set Background */
    for (zi = 0; zi < ifrm->frame_wdth * ifrm->frame_hght; zi++)
    {
        ifrm->pixel_list[zi].ccr = 255;
        ifrm->pixel_list[zi].ccg = 255;
        ifrm->pixel_list[zi].ccb = 255;
        ifrm->pixel_list[zi].cca = 255;
    }

    dst_32 = cfid->fst_dst;
    idx_32 = cfid->idx_lst;

    for (zi = 0; zi < cfid->fst_idx_sz; zi++)
    {
        pxl_idx_f = (int32_t) zi * ifrm->frame_hght / (int32_t) cfid->fst_idx_sz;

        for (ii = dst_32[zi]; ii< dst_32[zi + 1]; ii++)
        {
            pxl_idx_s = idx_32[ii] * (int32_t) ifrm->frame_wdth / (int32_t) cfid->snd_idx_sz;

        #ifdef DEBUG_INFO
            printf(" %7d %7d\n", pxl_idx_f, pxl_idx_s);
        #endif

            for (ij = 0; ij < draw_bkz_h; ij++)
            {
                ifrm_pxl_idx_f = pxl_idx_f + ij;

                if (ifrm_pxl_idx_f >= (int32_t) ifrm->frame_hght)
                {
                    ifrm_pxl_idx_f = (int32_t) ifrm->frame_hght - 1;
                }

                for (ik = 0; ik < draw_bkz_w; ik++)
                {
                    ifrm_pxl_idx_s = pxl_idx_s + ik;

                    if (ifrm_pxl_idx_s >= (int32_t) ifrm->frame_wdth)
                    {
                        ifrm_pxl_idx_s = (int32_t) ifrm->frame_wdth - 1;
                    }

                    ifrm_pxl_idx = ifrm_pxl_idx_f * (int32_t) ifrm->frame_wdth + ifrm_pxl_idx_s;

                    ifrm->pixel_list[ifrm_pxl_idx].ccr = 0;
                    ifrm->pixel_list[ifrm_pxl_idx].ccg = 0;
                    ifrm->pixel_list[ifrm_pxl_idx].ccb = 255;
                    ifrm->pixel_list[ifrm_pxl_idx].cca = 255;
                }
            }
        }
    }

    return ierr;
}


int render_sparsity_i64(const cfid_t *cfid, img_frame_t *ifrm)
{
    int ierr = ERRNO_UNDEFINED;

    size_t zi;
    int ii, ij, ik;

    int pxl_idx_f;
    int pxl_idx_s;
    int draw_bkz_w;  /* Draw block size of pixel (at least 1) for width */
    int draw_bkz_h;  /* Draw block size of pixel (at least 1) for height */

    int ifrm_pxl_idx_f;
    int ifrm_pxl_idx_s;
    int ifrm_pxl_idx;

    int64_t *dst_64, *idx_64;

    draw_bkz_w = ifrm->frame_wdth / (int) cfid->snd_idx_sz;

    if (draw_bkz_w == 0)
    {
        draw_bkz_w = 1;
    }

    draw_bkz_h = ifrm->frame_hght / (int) cfid->fst_idx_sz;

    if (draw_bkz_h == 0)
    {
        draw_bkz_h = 1;
    }

#ifdef DEBUG_INFO
    printf("Drawing Block Size: %d by %d\n", draw_bkz_w, draw_bkz_h);
#endif

    /* Set Background */
    for (zi = 0; zi < ifrm->frame_wdth * ifrm->frame_hght; zi++)
    {
        ifrm->pixel_list[zi].ccr = 255;
        ifrm->pixel_list[zi].ccg = 255;
        ifrm->pixel_list[zi].ccb = 255;
        ifrm->pixel_list[zi].cca = 255;
    }

    dst_64 = cfid->fst_dst;
    idx_64 = cfid->idx_lst;

    for (zi = 0; zi < cfid->fst_idx_sz; zi++)
    {
        pxl_idx_f = (int64_t) zi * ifrm->frame_hght / (int64_t) cfid->fst_idx_sz;

        for (ii = dst_64[zi]; ii< dst_64[zi + 1]; ii++)
        {
            pxl_idx_s = idx_64[ii] * (int64_t) ifrm->frame_wdth / (int64_t) cfid->snd_idx_sz;

        #ifdef DEBUG_INFO
            printf(" %7d %7d\n", pxl_idx_f, pxl_idx_s);
        #endif

            for (ij = 0; ij < draw_bkz_h; ij++)
            {
                ifrm_pxl_idx_f = pxl_idx_f + ij;

                if (ifrm_pxl_idx_f >= (int64_t) ifrm->frame_hght)
                {
                    ifrm_pxl_idx_f = (int64_t) ifrm->frame_hght - 1;
                }

                for (ik = 0; ik < draw_bkz_w; ik++)
                {
                    ifrm_pxl_idx_s = pxl_idx_s + ik;

                    if (ifrm_pxl_idx_s >= (int64_t) ifrm->frame_wdth)
                    {
                        ifrm_pxl_idx_s = (int64_t) ifrm->frame_wdth - 1;
                    }

                    ifrm_pxl_idx = ifrm_pxl_idx_f * (int64_t) ifrm->frame_wdth + ifrm_pxl_idx_s;

                    ifrm->pixel_list[ifrm_pxl_idx].ccr = 0;
                    ifrm->pixel_list[ifrm_pxl_idx].ccg = 0;
                    ifrm->pixel_list[ifrm_pxl_idx].ccb = 255;
                    ifrm->pixel_list[ifrm_pxl_idx].cca = 255;
                }
            }
        }
    }

    return ierr;
}


int render_sparsity(const cfid_t *cfid, uint8_t ityp, img_frame_t *ifrm)
{
    int ierr = ERRNO_UNDEFINED;

    switch (ityp)
    {
        case DID_HEAD_INDEX_T_I32:
            ierr = render_sparsity_i32(cfid, ifrm);
            break;
        case DID_HEAD_INDEX_T_I64:
            ierr = render_sparsity_i64(cfid, ifrm);
            break;
        default:
            ierr = ERRNO_UNKNOWN_TYPE;
            break;
    }
    return ierr;
}


int save_img_frame_png(const img_frame_t *ifrm, const char *fnof)
{
    int ierr = ERRNO_UNDEFINED;
    size_t zi, zj;
    FILE *fpof;

    png_structp png_ptr = NULL;
    png_infop inf_ptr = NULL;

    int pxl_size;
    int bit_depth;

    png_byte **row_ptrs = NULL;

    fpof = fopen(fnof, "wb");
    if (fpof == NULL)
    {
        printf("Open To Write Failed!\n");
        goto EXIT_SAVE_IMG_FRAME_PNG;
    }

    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp) NULL, NULL, NULL);
    if (png_ptr == NULL)
    {
        printf("PNG Create Write Struct Failed!\n");
        goto EXIT_SAVE_IMG_FRAME_PNG_STRUCT;
    }

    inf_ptr = png_create_info_struct(png_ptr);
    if (inf_ptr == NULL)
    {
        printf("PNG Create Info Struct Failed!\n");
        goto EXIT_SAVE_IMG_FRAME_PNG_INFO;
    }

    /* Setup Error Handling. */
    if (setjmp(png_jmpbuf(png_ptr)))
    {
        goto EXIT_SAVE_IMG_FRAME_PNG_INFO;
    }

    /* Set Attributions */
    pxl_size = 4;
    bit_depth = 8;

    png_set_IHDR(png_ptr, inf_ptr, ifrm->frame_wdth, ifrm->frame_hght, bit_depth, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);

    row_ptrs = png_malloc(png_ptr, ifrm->frame_hght * sizeof(png_byte*));
    for (zi = 0; zi < ifrm->frame_hght; zi++)
    {
        row_ptrs[zi] = (png_byte*) png_malloc(png_ptr, sizeof(uint8_t) * ifrm->frame_wdth * pxl_size);

        for (zj = 0; zj < ifrm->frame_wdth; zj++)
        {
            row_ptrs[zi][zj * pxl_size] = ifrm->pixel_list[zi * ifrm->frame_wdth + zj].ccr;
            row_ptrs[zi][zj * pxl_size + 1] = ifrm->pixel_list[zi * ifrm->frame_wdth + zj].ccg;
            row_ptrs[zi][zj * pxl_size + 2] = ifrm->pixel_list[zi * ifrm->frame_wdth + zj].ccb;
            row_ptrs[zi][zj * pxl_size + 3] = ifrm->pixel_list[zi * ifrm->frame_wdth + zj].cca;
        }
    }

    png_init_io(png_ptr, fpof);
    png_set_rows(png_ptr, inf_ptr, row_ptrs);
    png_write_png(png_ptr, inf_ptr, PNG_TRANSFORM_IDENTITY, NULL);

    for (zi = 0; zi < ifrm->frame_hght; zi++)
    {
        png_free(png_ptr, row_ptrs[zi]);
    }
    png_free(png_ptr, row_ptrs);

    EXIT_SAVE_IMG_FRAME_PNG_INFO:
    png_destroy_write_struct(&png_ptr, &inf_ptr);

    EXIT_SAVE_IMG_FRAME_PNG_STRUCT:
    fclose(fpof);

    EXIT_SAVE_IMG_FRAME_PNG:

    return ierr;
}
